<html>
<head>
<title>JavaScript OT</title>
<script src="../../common.pbjson.js" type="text/javascript"></script>
<script src="../../jsot.js" type="text/javascript"></script>
</head>
<body>
	<h1>Hello world</h1>
	<div><input type="button" value="Update" onclick="var result = doc.toString(); document.getElementById('out').innerHTML = ''; document.getElementById('out').appendChild( document.createTextNode(result) );"></div>
	<div id="out"></div>
	<div><input type="button" value="Bold" onclick="editor.bold();"></div>
	<div id="out2" contentEditable="true" style="border:1px solid black"></div>
	<br><br>
	<div id="out3" contentEditable="true" style="border:1px solid blue"></div>
	<script type="text/javascript">
	
function toDom( doc )
{
	var dom = document.getElementById("out2");
	dom.innerHTML = "";
	var line = null;
	var lineEmpty = false;
	var inline = false;
	var lineno = 0;
	for( var i = 0; i < editor.doc.content.length; ++i )
	{
		var c = editor.doc.content[i];
		if ( typeof(c) == "string" )
		{
			lineEmpty = false;
			if ( inline || !line )
				throw "Malformed doc, characters outside of line";
			var t = document.createTextNode( c );
			var f = editor.doc.format[i];
			if ( f )
			{				
				var span = document.createElement("span");
				span.appendChild(t);
				for( var a in f )
				{
					span.style[a] = f[a];
				}
				line.appendChild( span );
			}
			else
				line.appendChild( t );
		}
		else if ( c.element_start )
		{
			if ( line && lineEmpty )
				line.appendChild( document.createElement("br") );
			if ( inline )
				throw "Malformed doc, nested lines";
			line = document.createElement("div");
			line.lineno = lineno++;
			dom.appendChild( line );
			inline = true;
		}
		else if ( c.element_end )
		{
			if ( !line || !inline )
				throw "Malformed doc, missing opening line tag";
			inline = false;
			lineEmpty = true;
		}
		else
			throw "Busted";
	}
}

/**
 * @param {HTMLElement} dom is the HTML element that is contentEditable=true.
 *
 * @constructor
 */
function DomIterator( dom )
{
	/**
	 * Number of the current line.
	 */
	this.lineno = -1;
	/**
	 * A HTMLElementDiv.
	 */
	this.line = null;
	/**
	 * Number of characters inside the line
	 */
	this.charCount;
	/**
	 * A div, span or text node.
	 */
	this.current = dom;
	/**
	 * Position inside a text node. Only relevant when this,current is a text node.
	 */
	this.index = 0;
	/**
	 * List of KeyValueUpdate objects.
	 */
	this.formatUpdate = null;
	/**
	 * A dictionary mapping style keys to their value.
	 */
	this.format = null;
}

DomIterator.prototype.dispose = function()
{
	delete this.current;
	delete this.formatUpdate;
	delete this.format;
	delete this.line;
};

/**
 * Move the iterator over a linebreak.
 */
DomIterator.prototype.skipLineBreak = function()
{
	if ( this.lineno == -1 )
	{
		this.line = this.current.childNodes[0];
	}
	else
	{
		this.finalizeLine();
		this.line = this.line.nextSibling;
	}
	if ( !this.line )
		throw "There is no more line to go to";
	this.current = this.line; //.firstChild;
	this.index = 0;
	this.charCount = 0;
	this.lineno++;
	this.line.lineno = this.lineno;
	if ( this.formatUpdate )
		this.setStyle( this.format, this.formatUpdate );
};

/**
 * Moves the iterator over a number of characters inside a line.
 */
DomIterator.prototype.skipChars = function( count )
{
	if ( this.lineno == -1 )
		throw "Must skip line break first";
	
	// Inside a text node?
	if ( this.current.nodeType == 3 )
	{
		var min = Math.min( this.current.data.length - this.index, count );
		this.index += min;
		this.charCount += min;
		count -= min;
		if ( count > 0 )
		{
			this.index = 0;
			var x = this.current.nextSibling;
			if ( x )
				this.current = x;
			else
			{
				if ( this.current.parentNode.nodeName != "SPAN" )
					throw "Expected a span in the DOM";
				this.current = this.current.parentNode.nextSibling;
			}
			this.skipChars( count );
		}
	}
	// Inside a HTML element?
	else if ( this.current.nodeType == 1 )
	{
		if ( this.current.childNodes.length == 0 )
			throw "Did not expect an empty span";
		// Reformat the span if we are supposed to perform a format update.
		if ( this.formatUpdate && this.current.nodeName == "SPAN" )
			this.updateSpanStyle(this.current, this.formatUpdate);
		this.current = this.current.firstChild;
		this.skipChars( count );
	}
	else
		throw "There are no more characters in the line to skip";
};

/**
 * Inserts characters at the current position. Uses the current format for these characters.
 */
DomIterator.prototype.insertChars = function( str )
{
	if ( this.lineno == -1 )
		throw "Must skip line break first";

	// Insert inside a text node?
	if ( this.current.nodeType == 3 )
	{
		this.current.insertData( this.index, str );
		this.index += str.length;
		this.charCount += str.length;
		return;
	}
	// At the beginning of a line?
	else if ( this.current.nodeName == "DIV" )
	{
		if ( this.current == this.line )
			this.removeBr();
		// Insert a new span at the beginning of the document
		if ( !this.current.firstChild || this.formatUpdate || this.current.firstChild.nodeType != 3 )
		{
			var span = document.createElement("span");
			this.setSpanStyle( span, this.formatUpdate );
			var t = document.createTextNode( str );
			span.appendChild(t);
			this.current.insertBefore( span, this.current.firstChild );
			this.current = t;
			this.index += str.length;
			this.charCount += str.length;
			return;
		}
		// Prepend to the existing text node
		var t = this.current.firstChild;
		t.insertData( 0, str );
		this.current = t;
		this.index = str.length;
		this.charCount += str.length;
		return;
	}
	// At the beginning of a span
	else
	{
		// The span has a text node? -> Use it
		if ( this.current.firstChild )
		{
			this.current = this.current.firstChild;
			this.index = 0;
			this.insertChars( str );
			return;
		}
		// Insert a new text node in the span
		var t = document.createTextNode( str );
		this.current.appendChild( t );
		this.current = t;
		this.index = str.length;
		this.charCount += str.length;
	}
};

/**
 * Sets the style for all characters which are inserted from here on. The update
 * will be applied to all skipped items.
 *
 * @param {object} format is a dictionary mapping style keys to their value.
 * @param {KeyValueUpdate[]} update is a list of style updates or null if nothing needs updating.
 */
DomIterator.prototype.setStyle = function( format, update )
{
	if ( this.lineno == -1 )
		throw "Must skip line break first";
	if ( this.current == this.line )
		this.removeBr();

	this.formatUpdate = update;
	this.format = format;
	
	// Changing the formatting inside a text node?
	if ( this.current.nodeType == 3 )
	{
		// The text node itself is inside a SPAN?
		if ( this.current.parentNode.nodeName == "SPAN" )
		{
			// Defensive programming. Should not happen
			if ( this.index == 0 )
			{
				this.current = this.current.parentNode;
				this.setStyle( format, update );
				return;
			}
			// At the end of this span? Go to its next sibling (if there is one)
			if ( this.index == this.current.data.length && !this.current.nextSibling && this.current.parentNode.nextSibling )
			{
				var next = this.current.parentNode.nextSibling;
				
				if ( format )
				{
					this.current = next;
					this.index = 0;
					this.setStyle( format, update );
				}
				else
				{
					// The next span has the same style? -> Join them
					if ( next.nodeType == 1 && this.compareStyles( this.current.parentNode, next ) )
					{
						while( next.firstChild )
						{
							var f = next.firstChild;
							next.removeChild( f );
							this.current.parentNode.appendChild( f );
						}
						next.parentNode.removeChild(next);
					}
					else
					{
						// This is the end of an annotation update. There is no need to do anything about the next node
						this.current = next;
						this.index = 0;
					}
				}
				return;
			}
			// Split the text node (if required)
			var t = this.splitTextNode( this.current, this.index );
			// Split the span
			var span = this.splitNodeBefore( this.current.parentNode, t );
			this.setSpanStyle( span, format );
			this.current = t || span;
			this.index = 0;
			return;
		}	
		else // Inside the DIV
		{
			// Wrap the text inside a span and repeat
			var span = document.createElement( "span" );
			this.current.parentNode.insertBefore( span, this.current );
			while( span.nextSibling && span.nextSibling.nodeType == 3 )
			{
				var t = span.nextSibling;
				t.parentNode.removeChild( t );
				span.appendChild(t);
			}
			this.current = span.firstChild;
			this.setStyle( format, update );
			return;
		}
	}
	else if ( this.current.nodeType == 1 )
	{
		if ( this.current.nodeName == "SPAN" )
		{
			if ( format )
				this.setSpanStyle( this.current, format );
		}
		else if ( this.current.nodeName == "DIV" )
		{
			if ( format )
			{
				if ( this.current.firstChild )
				{
					this.current = this.current.firstChild;
					this.setStyle( format, update );
				}
				else
				{
					var span = document.createElement( "span" );
					this.setSpanStyle( span, format );
					this.current.appendChild(span);
					this.current = span;
				}
			}
		}
		else
			throw "Busted";
	}
	else
		throw "Busted";
};

DomIterator.prototype.deleteLineBreak = function()
{
	if ( this.lineno == -1 )
		throw "Deleting the first line tag is not allowed";
	if ( this.current == this.line )
		this.removeBr();

	var l = this.line.nextSibling;
	if ( !l )
		throw "There is no more line to go to";
	this.removeBr( l );
	while( l.firstChild )
	{
		var f = l.firstChild;
		l.removeChild(f);
		this.line.appendChild(f);
	};
	l.parentNode.removeChild(l);
	if ( this.formatUpdate )
		this.setStyle( this.format, this.formatUpdate );
};

DomIterator.prototype.deleteChars = function( count )
{
	if ( this.lineno == -1 )
		throw "Deleting chars before the first line tag is not allowed";
	if ( this.current == this.line )
		this.removeBr();
  
	// Inside a text node
	if ( this.current.nodeType == 3 )
	{
		var data = this.current.data;
		var min = Math.min( data.length - this.index, count );
		count -= min;
		// Delete the entire text node?
		if ( this.index == 0 && min == data.length )
		{
			var t = this.current;
			var p = t.parentNode;
			this.index = 0;
			this.current = t.previousSibling;
			// Delete the text node
			p.removeChild(t);
			if ( !this.current && p.tagName == "SPAN" )
			{
				// There is no text node on the left. Go to the left sibling of the parent span
				this.current = p.previousSibling;
				// Is the SPAN itself empty
				if ( !p.firstChild )
					p.parentNode.removeChild(p);
			}
			if ( !this.current )
				// There is nothing on the left. Go to the beginning of the line
				this.current = this.line;
			// On a SPAN? -> Go to the right end of the span
			if ( this.current.nodeType == 1 && this.current.nodeName == "SPAN" )
				this.current = this.current.lastChild;			
			if ( this.current.nodeType == 3 )
				// Go to the right end of the text node
				this.index = this.current.data.length;
			// Need to delete more?
			if ( count > 0 )
				this.deleteChars( count );
		}
		// Delete only some part of the text node?
		else
		{
			this.current.deleteData( this.index, min );
			if ( count > 0 )
			{
				this.index = 0;
				var t = this.current.nextSibling;
				if ( t )
					this.current = t;
				else
					this.current = this.current.parentNode.nextSibling;
				this.deleteChars( count );
			}
		}
	}
	else if ( this.current.nodeType == 1 )
	{
		if ( this.current.childNodes.length == 0 )
			throw "Did not expect an empty span";
		this.current = this.current.firstChild;
		this.deleteChars( count );
	}
	else
		throw "There are no more characters in the line to delete";
};

/**
 * Splits a text node and inserts a new text node right of it. The split is at character
 * number pos. The new text node is retuned.
 * If pos is zero, the function does nothing and returns node.
 * If pos is at then end of the text node, the function does nothing and returns the nextSibling
 * of node, which can be null.
 *
 * @return a text node or null.
 */
DomIterator.prototype.splitTextNode = function(node, pos)
{
	if ( pos == 0 )
		return node;
	var data = node.data;
	if ( data.length == pos )
		return node.nextSibling;
	var t = document.createTextNode( data.substring( pos, data.length ) );
	node.data = data.substr( 0, pos );
	node.parentNode.insertBefore( t, node.nextSibling );
	return t;
};

/**
 * @return a new HTML element of the same tagName as node. All children of node starting with
 *         child number pos is moved to the new HTML element. Finally, the new HTML element
 *         is inserted as the next sibling of node.
 */
DomIterator.prototype.splitNodeBefore = function(node, before)
{
	var el = document.createElement( node.nodeName );
	var b = before;
	while( b )
	{
		var n = b.nextSibling;
		node.removeChild( b );
		el.appendChild( b );
		b = n;
	}
	node.parentNode.insertBefore( el, node.nextSibling );
	return el;
};

DomIterator.prototype.insertLineBreak = function(element)
{
	// The document is empty?
	if ( !this.line )
	{
		this.line = document.createElement("div");
		this.current.appendChild(this.line);
		this.current = this.line;
		this.index = 0;
		this.lineno = 0;
		this.line.lineno = this.lineno;
		this.charCount = 0;
		return;
	}
	
	var node = this.current;
	var before;
	if ( this.current.nodeType == 3 )
	{
		before = this.splitTextNode( node, this.index );
		node = node.parentNode;
	}
	else
		before = this.current.firstChild;
	if ( node.nodeName == "SPAN" )
	{
		if ( !before )
			before = node.nextSibling;
		else
		{
			before = this.splitNodeBefore( node, before );
			before.class = "split";
			this.copySpanStyle( before, node );
		}
		node = node.parentNode;
	}
	if ( node.nodeName != "DIV" )
		throw "Expected DIV";
	before = this.splitNodeBefore( node, before );
	this.finalizeLine();
	this.line = before;
	this.current = this.line;
	this.index = 0;
	this.lineno++;
	this.line.lineno = this.lineno;
	this.charCount = 0;
	if ( this.line.firstChild && this.line.firstChild.nodeType == 1 && this.line.firstChild.class == "split" )
	{
		this.current = this.line.firstChild;
		this.current.class = null;
	}
	else if ( this.formatUpdate )
		this.setStyle( this.format, this.formatUpdate );
};

DomIterator.prototype.finalizeLine = function()
{
	if ( !this.line )
		return;
		
	// Last span is empty?
	if ( this.line.lastChild && this.line.lastChild.nodeType == 1 && this.line.lastChild.childNodes.length == 0 )	
		this.line.removeChild( this.line.lastChild );
	
	// Empty line?
	if ( !this.line.firstChild )
	{
		this.line.appendChild( document.createElement("br") );
	}
};

/**
  * @param l is optional. It is the <div> tag of a line. If not specified, the current line is used.
  */
DomIterator.prototype.removeBr = function(l)
{
	if ( !l ) l = this.line;
	if ( l && l.firstChild && l.firstChild.nodeType == 1 && l.firstChild.nodeName == "BR" )
		l.removeChild( l.firstChild );
};

DomIterator.prototype.updateSpanStyle = function( span, update )
{
	if ( !update )
		return;
	for( var key in update )
	{
		if ( key == "style/backgroundColor" )
			span.backgroundColor = update[key].new_value;
		if ( key == "style/color" )
			span.color = update[key].new_value;
		if ( key == "style/fontFamily" )
			span.fontFamily = update[key].new_value;
		if ( key == "style/fontSize" )
			span.fontSize = update[key].new_value;
		if ( key == "style/fontStyle" )
			span.fontStyle = update[key].new_value;
		if ( key == "style/fontWeight" )
			span.fontWeight = update[key].new_value;
		if ( key == "style/textDecoration" )
			span.textDecoration = update[key].new_value;
		if ( key == "style/verticalAlign" )
			span.verticalAlign = update[key].new_value;
	}
};

DomIterator.prototype.setSpanStyle = function( span, format )
{
	if ( format && format["style/backgroundColor"] )
		span.style.backgroundColor = format["style/backgroundColor"];
	else
		span.style.backgroundColor = null;
	if ( format && format["style/color"] )
		span.style.color = format["style/color"];
	else
		span.style.color = null;
	if ( format && format["style/fontFamily"] )
		span.style.fontFamily = format["style/fontFamily"];
	else
		span.style.fontFamily = null;	
	if ( format && format["style/fontWeight"] )
		span.style.fontWeight = format["style/fontWeight"];
	else
		span.style.fontWeight = null;
	if ( format && format["style/fontStyle"] )
		span.style.fontStyle = format["style/fontStyle"];
	else
		span.style.fontStyle = null;
	if ( format && format["style/fontSize"] )
		span.style.fontSize = format["style/fontSize"];
	else
		span.style.fontSize = null;
	if ( format && format["style/textDecoration"] )
		span.style.textDecoration = format["style/textDecoration"];
	else
		span.style.textDecoration = null;	
	if ( format && format["style/verticalAlign"] )
		span.style.verticalAlign = format["style/verticalAlign"];
	else
		span.style.verticalAlign = null;
};

DomIterator.prototype.copySpanStyle = function( dest, source )
{
	dest.style.fontWeight = source.style.fontWeight;
	dest.style.fontSize = source.style.fontSize;
	dest.style.fontStyle = source.style.fontStyle;
};

DomIterator.prototype.compareStyles = function( span1, span2 )
{
	return span1.style.fontWeight == span2.style.fontWeight && span1.style.fontStyle == span2.style.fontStyle && span1.style.fontSize == span2.style.fontSize;
};

DomIterator.prototype.gotoEndOfLine = function()
{
	// Empty document?
	if ( !this.line )
		return;
	this.index = 0;
	this.current = this.line.lastChild;
	// The line is empty?
	if ( !this.current || (this.current.nodeType == 1 && this.current.nodeName == "BR" ) )
	{
		this.current = this.line;
		return;
	}
	// Currently on a span?
	if( this.current.nodeType == 1 )
	{
		// Span is empty?
		if ( !this.current.lastChild )
			return;
		this.current = this.current.lastChild;
	}
	this.index = this.current.data.length;
};

DomIterator.prototype.isEndOfLine = function()
{
	// Empty document?
	if ( !this.line )
		return true;
	var node = this.current;
	do
	{
		if ( node.nodeType == 3 )
		{
			if ( this.index < node.data.length )
				return false;
		}
		else if ( node.firstChild && !(node.firstChild.nodeType == 1 && node.firstChild.nodeName == "BR" ) )
			return false;
		if ( node.nodeName == "DIV" )
			return true;
		if ( node.nextSibling )
			return false;
		node = node.parentNode.nextSibling;
	} while( node );
	
	return true;
};

DomIterator.prototype.isEndOfDocument = function()
{
	// Empty document?
	if ( !this.line )
		return true;
	if ( line.nextSibling )
		return false;
	return this.isEndOfLine();
};

OTListener = function(dom, session, jid)
{
	this.dom = dom;
	this.suspend = false;
	this.cursorAnnoKey = "user/e/" + session;
	this.cursorAnnoValue = jid;
	this.hasSelection = false;
};

OTListener.prototype.begin = function( doc )
{
	if ( this.suspend )
		return;
//	this.suspend = true;
//	this.hasSelection = this.editor.markSelection();
//	this.suspend = false;
	delete this.cursor;
	this.doc = doc;
	this.it = new DomIterator( this.dom );
}

OTListener.prototype.retainElementStart = function( element, format )
{
	if ( this.suspend )
		return;
	this.it.format = format;
	if ( element.type == "line" )
		this.it.skipLineBreak();
};

OTListener.prototype.retainElementEnd = function( element, format )
{
	if ( this.suspend )
		return;
	this.it.format = format;
};

OTListener.prototype.insertElementStart = function( element, format )
{
	if ( this.suspend )
		return;
	this.it.format = format;
	this.checkForCursor( format );
	if ( element.type == "line" )
		this.it.insertLineBreak( element );
};

OTListener.prototype.insertElementEnd = function( element, format )
{
	if ( this.suspend )
		return;
	this.it.format = format;
};

OTListener.prototype.deleteElementStart = function( element, format )
{
	if ( this.suspend )
		return;
	this.it.format = format;
	this.checkForCursor( format );
	if ( element.type == "line" )
		this.it.deleteLineBreak();
};

OTListener.prototype.deleteElementEnd = function(format)
{
	if ( this.suspend )
		return;
	this.it.format = format;
};

OTListener.prototype.insertCharacters = function( chars, format )
{
	if ( this.suspend )
		return;
	this.it.format = format;
	this.checkForCursor( format );
	this.it.insertChars( chars );
};

OTListener.prototype.deleteCharacters = function( chars, format )
{
	if ( this.suspend )
		return;
	this.it.format = format;
	this.checkForCursor( format );
	this.it.deleteChars( chars.length );
};

OTListener.prototype.retainCharacters = function( count, format )
{
	if ( this.suspend )
		return;
	this.it.format = format;
	this.checkForCursor( format );
	this.it.skipChars( count );
};

OTListener.prototype.updateAttributes = function( element, format )
{
	if ( this.suspend )
		return;
	this.it.format = format;
	this.checkForCursor( format );
};

OTListener.prototype.replaceAttributes = function( element, format )
{
	if ( this.suspend )
		return;
	this.it.format = format;
	this.checkForCursor( format );
};

OTListener.prototype.annotationBoundary = function( update, format )
{
	if ( this.suspend )
		return;
	this.checkForCursor( format );
	this.it.setStyle( format, update );
};

OTListener.prototype.end = function()
{
	if ( this.suspend )
		return;	
	this.it.finalizeLine();
	
	if ( !this.cursor )
		this.cursor = { line : this.it.line, lineno : this.it.lineno, charCount : this.it.charCount };

	if ( this.hasSelection )
		this.editor.showSelection();
	
	this.it.dispose();
	delete this.it;
};

OTListener.prototype.setSuspend = function( suspend )
{
	this.suspend = suspend;
};

OTListener.prototype.checkForCursor = function(format)
{
	if ( !this.cursor && format && format[ this.cursorAnnoKey ] == this.cursorAnnoValue )
		this.cursor = { line : this.it.line, lineno : this.it.lineno, charCount : this.it.charCount };
}



function Editor(doc, dom, session, jid)
{
	this.dom = dom;
	this.doc = doc;
	this.session = session;
	this.jid = jid;
	var self = this;
	dom.onkeypress = function(e) { self.keypress(e); }
	dom.onkeydown = function(e) { self.keydown(e); }
	
	this.listener = new OTListener( this.dom, session, jid );
	this.listener.editor = this;
	this.doc.addListener( this.listener );
}

Editor.prototype.keydown = function(e)
{
	window.console.log("KeyDown = " + e.keyIdentifier.toString() + " code=" + e.keyCode.toString());

	if ( e.keyCode != 8 && e.keyCode != 46 )
		return;
	
	var sel = window.getSelection();
	var selDom = sel.anchorNode;
	var selOffset = sel.anchorOffset;
	
	var charCount = selDom.nodeType == 3 ? selOffset : 0;
	var lineno = 0;
	// Find the line, i.e. <div> element
	var line = selDom;
	while( line.nodeType != 1 || line.nodeName != "DIV" )
	{
		var p = line.previousSibling;
		while( p )
		{
			charCount += this.charCount(p);
			p = p.previousSibling;
		}
		line = line.parentNode;
	}
	// Find the line number
	var l = line.previousSibling;
	while( l )
	{
		lineno++;
		l = l.previousSibling;
	}
	
	window.console.log("Line No " + line.lineno.toString() + " and pos " + charCount.toString() );
	
	// Delete selection?
	if ( !sel.isCollapsed )
	{
		window.console.log("DeleteSelection");
		e.stopPropagation();
		e.preventDefault();

		this.deleteSelection();
		return;
	}
		
	// Backspace?
	if ( e.keyCode == 8 )
	{
		var element = this.getElementByLineNo( lineno );

		// Delete a linebreak?
		if ( charCount == 0 )
		{
			e.stopPropagation();
			e.preventDefault();

			// At the beginning of the document? -> Do nothing
			if ( !line.previousSibling )
				return;

			var iter = new DomIterator( this.dom );
			iter.line = line.previousSibling;
			iter.lineno = lineno - 1;
			iter.current = iter.line;
			iter.index = 0;
			iter.gotoEndOfLine();
			iter.deleteLineBreak();
			iter.finalizeLine();
			
			// Position the cursor
			if ( iter.current.nodeType == 1 )
				sel.collapse( iter.current, 0 );
			else
				sel.collapse( iter.current, iter.index );
			
			var pos = element.itemCountBefore();
			this.listener.setSuspend(true);
			var ops = new protocol.ProtocolDocumentOperation();
			ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( pos ) );
			ops.component.push( protocol.ProtocolDocumentOperation.newDeleteElementStart("line") );
			ops.component.push( protocol.ProtocolDocumentOperation.newDeleteElementEnd() );
			ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( this.doc.itemCount() - pos - 2 ) );
			ops.applyTo( this.doc );
			this.listener.setSuspend(false);
			return;
		}
		else
		{
			var pos = element.itemCountBefore() + 2 + charCount - 1;
			var ch = doc.getCharAt(pos);
			window.console.log("Backspace " + ch);
			this.listener.setSuspend(true);
			var ops = new protocol.ProtocolDocumentOperation();
			ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( pos ) );
			ops.component.push( protocol.ProtocolDocumentOperation.newDeleteCharacters( ch ) );
			ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( this.doc.itemCount() - pos - 1 ) );
			ops.applyTo( this.doc );
			this.listener.setSuspend(false);
			
			// Chrome inserts some tags when a line becomes empty. Don't want this.
			// Thus, I do the deletion myself and end up with a nice <div><br><div>
			if ( this.charCount( line ) == 1 )
			{
				e.stopPropagation();
				e.preventDefault();
				
				line.innerHTML = "<br>";
				sel.collapse( line, 0 );
			}			
			return;
		}
	}
	// Delete?
	else if ( e.keyCode == 46 )
	{
		var element = this.getElementByLineNo( lineno );
		var pos = element.itemCountBefore() + 2 + charCount;

		var iter = new DomIterator( this.dom );
		iter.line = line;
		iter.lineno = lineno;
		iter.current = selDom;
		iter.index = selOffset;
		
		if ( iter.isEndOfLine() )
		{		  
			e.stopPropagation();
			e.preventDefault();

			// End of document? -> Do nothing
			if ( !iter.line.nextSibling )
				return;
			
			iter.deleteLineBreak();
			iter.finalizeLine();
			
			// Position the cursor
			if ( iter.current.nodeType == 1 )
				sel.collapse( iter.current, 0 );
			else
				sel.collapse( iter.current, iter.index );

			window.console.log("Delete line break" + ch);
			this.listener.setSuspend(true);
			var ops = new protocol.ProtocolDocumentOperation();
			ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( pos ) );
			ops.component.push( protocol.ProtocolDocumentOperation.newDeleteElementStart("line") );
			ops.component.push( protocol.ProtocolDocumentOperation.newDeleteElementEnd() );
			ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( this.doc.itemCount() - pos - 2 ) );
			ops.applyTo( this.doc );
			this.listener.setSuspend(false);
			return;
		}
		else
		{
			var ch = doc.getCharAt(pos);
			window.console.log("Delete " + ch);
			this.listener.setSuspend(true);
			var ops = new protocol.ProtocolDocumentOperation();
			ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( pos ) );
			ops.component.push( protocol.ProtocolDocumentOperation.newDeleteCharacters( ch ) );
			ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( this.doc.itemCount() - pos - 1 ) );
			ops.applyTo( this.doc );
			this.listener.setSuspend(false);

			// Chrome inserts some tags when a line becomes empty. Don't want this.
			// Thus, I do the deletion myself and end up with a nice <div><br><div>
			if ( this.charCount( line ) == 1 )
			{
				e.stopPropagation();
				e.preventDefault();
				
				line.innerHTML = "<br>";
				sel.collapse( line, 0 );
			}
			return;
		}
	}
	else
		throw "Unsupported keycode";
};

Editor.prototype.keypress = function(e)
{
	window.console.log("Key Press = " + e.keyIdentifier);

	var sel = window.getSelection();
	var selDom = sel.anchorNode;
	var selOffset = sel.anchorOffset;
	
	var charCount = selDom.nodeType == 3 ? selOffset : 0;
	var lineno = 0;
	// Find the line, i.e. <div> element
	var line = selDom;
	while( line.nodeType != 1 || line.nodeName != "DIV" )
	{
		var p = line.previousSibling;
		while( p )
		{
			charCount += this.charCount(p);
			p = p.previousSibling;
		}
		line = line.parentNode;
	}
	// Find the line number
	var l = line.previousSibling;
	while( l )
	{
		lineno++;
		l = l.previousSibling;
	}
	
	window.console.log("Line No " + line.lineno.toString() + " and pos " + charCount.toString() );
	
	if ( e.keyIdentifier == "Enter" || e.keyCode == 13)
	{
		window.console.log("Return");
		e.stopPropagation();
		e.preventDefault();

		var iter = new DomIterator( this.dom );
		iter.line = line;
		iter.lineno = lineno;
		iter.current = selDom;
		iter.index = selOffset;
		
		// Insert a line break
		iter.insertLineBreak();
		iter.finalizeLine();
		
		// Position the cursor
		if ( iter.current.nodeType == 1 )
			sel.collapse( iter.current, 0 );	
		else
			sel.collapse( iter.current, iter.index );
			
		var element = this.getElementByLineNo( lineno );
		var pos = element.itemCountBefore() + 2 + charCount;
	
		this.listener.setSuspend(true);
		var ops = new protocol.ProtocolDocumentOperation();
		ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( pos ) );
		ops.component.push( protocol.ProtocolDocumentOperation.newElementStart("line") );
		ops.component.push( protocol.ProtocolDocumentOperation.newElementEnd() );
		ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( this.doc.itemCount() - pos ) );
		ops.applyTo( this.doc );
		this.listener.setSuspend(false);
		return;
	}
	
	this.listener.setSuspend(true);
	var element = this.getElementByLineNo( lineno );
	var pos = element.itemCountBefore() + 2 + charCount;
	window.console.log("Before = " + element.itemCountBefore().toString() + " charCount=" + charCount.toString() );
	var ops = new protocol.ProtocolDocumentOperation();
	ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( pos ) );
	ops.component.push( protocol.ProtocolDocumentOperation.newCharacters( String.fromCharCode(e.keyCode) ) );
	ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( this.doc.itemCount() - pos ) );
	ops.applyTo( this.doc );
	this.listener.setSuspend(false);
};

Editor.prototype.getElementByLineNo = function( lineno )
{
	var l = 0;
	for( var i = 0; i < this.doc.content.length; ++i )
	{
		var c = this.doc.content[i];
		if ( typeof(c) == "string" )
			continue;
		if ( c.element_start && c.type == "line" )
		{
			if ( l == lineno ) return c;
			l++;
		}
	}
	return null;
};

/**
 * @return the number of characters in a HTML node.
 */
Editor.prototype.charCount = function( node )
{
	if ( node.nodeType == 3 )
		return node.data.length;
	var result = 0;
	var c = node.firstChild;
	while( c )
	{
		result += this.charCount(c);
		c = c.nextSibling;
	}
	return result;
};

Editor.prototype.isEmptyElement = function(node)
{
	if ( node.nodeType == 3 )
		return node.data.length == 0;
	for( var i = 0; i < node.childNodes.length; ++i )
		if ( !this.isEmptyElement( node.childNodes[i] ) )
			return false;
	return true;
};

Editor.prototype.bold = function()
{
	this.markSelection();
	
	var sel = window.getSelection();
	var selDom = sel.anchorNode;
	var selOffset = sel.anchorOffset;
	
	if ( sel.isCollapsed )
		return;

	var pos1 = this.getLinePosition( sel.anchorNode, sel.anchorOffset );
	var pos2 = this.getLinePosition( sel.focusNode, sel.focusOffset );
	
	if ( pos2.lineno < pos1.lineno || ( pos2.lineno == pos1.lineno && pos2.charCount < pos1.charCount ) )
	{
		var tmp = pos2;
		pos2 = pos1;
		pos1 = tmp;
	}
	
	var docpos1 = this.getDocPosition( pos1.lineno, pos1.charCount );
	var docpos2 = this.getDocPosition( pos2.lineno, pos2.charCount );

	var style = false;

	var ops = new protocol.ProtocolDocumentOperation();
	ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( docpos1 ) );
	for( var i = docpos1; i < docpos2; ++i )
	{
		var format = this.doc.getFormatAt( i );
		var s = ( format && format["style/fontWeight"] == "bold" );
		if ( (s && style) || (!s && !style) )
		{
			var ends = [];
			var begins = [];
			if ( s && style )
			{
				ends.push( "style/fontWeight" );
				style = false;
			}
			else if ( !s && !style )
			{
				begins.push( protocol.ProtocolDocumentOperation.newKeyValueUpdate( "style/fontWeight", null, "bold" ) );
				style = true;
			}
			ops.component.push( protocol.ProtocolDocumentOperation.newAnnotationBoundary( ends, begins ) );
		}
		ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( 1 ) );
	}
	if ( style )
		ops.component.push( protocol.ProtocolDocumentOperation.newAnnotationBoundary( [ "style/fontWeight" ], [  ] ) );
	ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( this.doc.itemCount() - docpos2 ) );
	ops.applyTo( this.doc );
	
	this.showSelection();
};

Editor.prototype.deleteSelection = function()
{
	var sel = window.getSelection();
	var selDom = sel.anchorNode;
	var selOffset = sel.anchorOffset;
	
	if ( sel.isCollapsed )
		return;

	this.markSelection();
	
	var pos1 = this.getLinePosition( sel.anchorNode, sel.anchorOffset );
	var pos2 = this.getLinePosition( sel.focusNode, sel.focusOffset );
	
	if ( pos2.lineno < pos1.lineno || ( pos2.lineno == pos1.lineno && pos2.charCount < pos1.charCount ) )
	{
		var tmp = pos2;
		pos2 = pos1;
		pos1 = tmp;
	}
	
	var element1 = this.getElementByLineNo( pos1.lineno );
	var docpos1 = element1.itemCountBefore() + 2 + pos1.charCount;
	var element2 = this.getElementByLineNo( pos2.lineno );
	var docpos2 = element2.itemCountBefore() + 2 + pos2.charCount;

	var ops = new protocol.ProtocolDocumentOperation();
	ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( docpos1 ) );
	for( var i = docpos1; i < docpos2; ++i )
	{
		var c = this.doc.getItemAt(i);
		if ( typeof(c) == "string" )
			ops.component.push( protocol.ProtocolDocumentOperation.newDeleteCharacters( c ) );
		else if ( c.element_start )
			ops.component.push( protocol.ProtocolDocumentOperation.newDeleteElementStartFromElement( c ) );
		else
			ops.component.push( protocol.ProtocolDocumentOperation.newDeleteElementEnd() );
	}
	ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( this.doc.itemCount() - docpos2 ) );
	ops.applyTo( this.doc );

	this.showSelection();
};

/**
 * Determines the line number and character position inside the line
 * from a given HTML node and offset. This helps in mapping HTML cursor positions
 * to a position in JSOT.Doc.
 */
Editor.prototype.getLinePosition = function(selDom, selOffset)
{
	var charCount = selDom.nodeType == 3 ? selOffset : 0;
	var lineno = 0;
	// Count the number of characters
	var line = selDom;
	while( line.nodeType != 1 || line.nodeName != "DIV" )
	{
		var p = line.previousSibling;
		while( p )
		{
			charCount += this.charCount(p);
			p = p.previousSibling;
		}
		line = line.parentNode;
	}
	// Find the line number
	var l = line.previousSibling;
	while( l )
	{
		lineno++;
		l = l.previousSibling;
	}
	
	return { line : line, lineno : lineno, charCount : charCount };
};

Editor.prototype.getDocPosition = function( lineno, charCount )
{
	var element = this.getElementByLineNo( lineno );
	return element.itemCountBefore() + 2 + charCount;
};

Editor.prototype.getDomPosition = function( line, lineno, charCount )
{
	var it = new DomIterator(this.dom);
	it.line = line;
	it.current = line;
	it.lineno = lineno;
	it.skipChars( charCount );
	return { node : it.current, offset : it.index };
};

Editor.prototype.markSelection = function()
{
	this.listener.setSuspend( true );
	
	// Delete the old selection
	if ( this.listener.cursor )
	{
		var cursorpos = this.getDocPosition( this.listener.cursor.lineno, this.listener.cursor.charCount );
		var ops = new protocol.ProtocolDocumentOperation();
		ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( cursorpos ) );
	  	ops.component.push( protocol.ProtocolDocumentOperation.newAnnotationBoundary( [ ], [ protocol.ProtocolDocumentOperation.newKeyValueUpdate( this.listener.cursorAnnoKey, this.listener.cursorAnnoValue, null ) ] ) );
		ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( this.doc.itemCount() - cursorpos ) );
		ops.applyTo( this.doc );
	}
	
	var sel = window.getSelection();
	var selDom = sel.focusNode;
	var selOffset = sel.focusOffset;
	
	// Is the current selection in this editor?
	var found = false;
	var x = selDom;
	while( x )
	{
		if ( x == this.dom )
		{
			found = true;
			break;
		}
		x = x.parentNode;
	}
	
	if ( found )
	{
		var pos = this.getLinePosition( selDom, selOffset );
		var cursorpos = this.getDocPosition( pos.lineno, pos.charCount );
		var ops = new protocol.ProtocolDocumentOperation();
		ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( cursorpos ) );
	  	ops.component.push( protocol.ProtocolDocumentOperation.newAnnotationBoundary( [ ], [ protocol.ProtocolDocumentOperation.newKeyValueUpdate( this.listener.cursorAnnoKey, null, this.listener.cursorAnnoValue ) ] ) );
		ops.component.push( protocol.ProtocolDocumentOperation.newRetainItemCount( this.doc.itemCount() - cursorpos ) );
		ops.applyTo( this.doc );		
	}
	
	this.listener.setSuspend( false );
};

Editor.prototype.showSelection = function()
{
	var sel = window.getSelection();
	var cursorPos = this.getDomPosition( this.listener.cursor.line, this.listener.cursor.lineno, this.listener.cursor.charCount );
	sel.collapse( cursorPos.node, cursorPos.offset );
};




var doc = new JSOT.Doc();

var editor = new Editor( doc, document.getElementById("out2"), "123", "Torben" );
var editor2 = new Editor( doc, document.getElementById("out3"), "789", "Horst" );

var ops = new protocol.ProtocolDocumentOperation();
ops.component.push( protocol.ProtocolDocumentOperation.newElementStart("line") );
ops.component.push( protocol.ProtocolDocumentOperation.newElementEnd() );
ops.component.push( protocol.ProtocolDocumentOperation.newCharacters("First") );
ops.component.push( protocol.ProtocolDocumentOperation.newElementStart("line") );
ops.component.push( protocol.ProtocolDocumentOperation.newElementEnd() );
ops.component.push( protocol.ProtocolDocumentOperation.newElementStart("line") );
ops.component.push( protocol.ProtocolDocumentOperation.newElementEnd() );
ops.component.push( protocol.ProtocolDocumentOperation.newAnnotationBoundary( [], [ protocol.ProtocolDocumentOperation.newKeyValueUpdate( "style/fontWeight", null, "bold" ) ] ) );
ops.component.push( protocol.ProtocolDocumentOperation.newCharacters("This is bold") );
ops.component.push( protocol.ProtocolDocumentOperation.newAnnotationBoundary( [ "style/fontWeight" ], [ ] ) );
ops.component.push( protocol.ProtocolDocumentOperation.newCharacters(" ") );
ops.component.push( protocol.ProtocolDocumentOperation.newAnnotationBoundary( [], [ protocol.ProtocolDocumentOperation.newKeyValueUpdate( "style/fontStyle", null, "italic" ) ] ) );
ops.component.push( protocol.ProtocolDocumentOperation.newCharacters("and italic") );
ops.component.push( protocol.ProtocolDocumentOperation.newAnnotationBoundary( [ "style/fontStyle" ], [ ] ) );
ops.component.push( protocol.ProtocolDocumentOperation.newElementStart("line") );
ops.component.push( protocol.ProtocolDocumentOperation.newElementEnd() );
ops.component.push( protocol.ProtocolDocumentOperation.newCharacters("A second line") );
ops.component.push( protocol.ProtocolDocumentOperation.newElementStart("line") );
ops.component.push( protocol.ProtocolDocumentOperation.newElementEnd() );
ops.component.push( protocol.ProtocolDocumentOperation.newCharacters("An ") );
ops.component.push( protocol.ProtocolDocumentOperation.newAnnotationBoundary( [], [ protocol.ProtocolDocumentOperation.newKeyValueUpdate( "style/fontStyle", null, "italic" ) ] ) );
ops.component.push( protocol.ProtocolDocumentOperation.newCharacters("italic") );
ops.component.push( protocol.ProtocolDocumentOperation.newAnnotationBoundary( [ "style/fontStyle" ], [ ] ) );
ops.component.push( protocol.ProtocolDocumentOperation.newCharacters(" line") );
ops.component.push( protocol.ProtocolDocumentOperation.newElementStart("line") );
ops.component.push( protocol.ProtocolDocumentOperation.newElementEnd() );
ops.component.push( protocol.ProtocolDocumentOperation.newCharacters("ABC DEF") );
ops.component.push( protocol.ProtocolDocumentOperation.newElementStart("line") );
ops.component.push( protocol.ProtocolDocumentOperation.newElementEnd() );
ops.component.push( protocol.ProtocolDocumentOperation.newCharacters("GHI JKL") );
ops.component.push( protocol.ProtocolDocumentOperation.newElementStart("line") );
ops.component.push( protocol.ProtocolDocumentOperation.newElementEnd() );
ops.component.push( protocol.ProtocolDocumentOperation.newCharacters("MNO PQR") );
ops.applyTo(doc);

var result = doc.toString();
document.getElementById("out").appendChild( document.createTextNode(result) );

	</script>
</body>
</html>